<!--
 * @Description: 终端连接
 * @Author: 0001100986/LiuShuai
 * @Date: 2021-02-02 11:21:17
 * @LastEditTime: 2021-10-29 18:37:26
 * @LastEditors: 0001100986/LiuShuai
-->
<template>
  <div id="indexContainer">
    <div id="telnetSSH" style="height: 100%;overflow-x: auto;"></div>
  </div>
</template>
<script>
import { Terminal } from 'xterm';
import 'xterm/css/xterm.css';
import { FitAddon } from 'xterm-addon-fit';
import $ from 'jquery';
export default {
  name: 'telnetssh',
  props: ['type'],
  data () {
    return {
      style: {},
      encoding: 'utf-8',
      term: null,
      decoder: window.TextDecoder ? new window.TextDecoder('utf-8') : 'utf-8',
      welcome: 0,
      rows: 40,
      cols: 100
    };
  },
  mounted () {
    let _this = this;
    _this.$nextTick(() => {
      // 初始化term
      const self = this;
      // 获取容器宽高/字号大小，定义行数和列数
      _this.rows = document.querySelector('#indexContainer').offsetHeight / 16 - 6;
      _this.cols = document.querySelector('#indexContainer').offsetWidth / 14;
      let terminalContainer = document.getElementById('telnetSSH');
      let term = new Terminal({
        // eslint-disable-next-line radix
        rows: parseInt(self.rows), // 行数
        // eslint-disable-next-line radix
        cols: parseInt(self.cols), // 不指定行数，自动回车后光标从下一行开始
        cursorBlink: true,
        theme: {
          background: 'black'
        },
        scrollback: 10000,
        fontSize: 14
      });
      term.open(terminalContainer);
      // canvas背景全屏
      const fitAddon = new FitAddon();
      term.loadAddon(fitAddon);
      fitAddon.fit();
      term.focus();
      _this.term = term;
      _this.term.onData(function(data) {
        if (self.socket) {
          self.socket.send(JSON.stringify({ 'data': data }));
        }
      });
      _this.term.onResize(function({ cols, rows }) {
        if (cols !== self.term.cols || rows !== self.term.rows) {
          self.term.resize(cols, rows);
          if (self.socket) {
            self.socket.send(JSON.stringify({ 'resize': [cols, rows] }));
          }
        }
      });
      $(window).resize(() => {
        if (_this.term) {
          _this.resize_terminal(_this.term);
        }
      });
    });
  },
  beforeDestroy () {
    this.socket.close();
    this.term.dispose();
    sessionStorage.removeItem('shellCPE');
  },
  methods: {
    initSocket (data) {
      const resp = {
        responseJSON: {
          id: data.connectionId,
          status: '200'
        },
        status: 200,
        statusText: 'ok',
        websocketHost: data.websocketHost,
        scheme: data.scheme
      };
      const msg = resp.responseJSON;
      const url = resp.scheme + '://' + resp.websocketHost + '/telnetssh/' + msg.id;
      this.socket = new window.WebSocket(url);
      // 心跳检测
      this.heartCheck = {
        timeout: 30000,   // 30秒发一次心跳
        timeoutObj: null,
        serverTimeoutObj: null,
        reset: function() {
          clearTimeout(this.timeoutObj);
          clearTimeout(this.serverTimeoutObj);
          return this;
        },
        start: function() {
          const self = this;
          this.timeoutObj = setTimeout(function() {
            if (this.socket && this.socket.readyState === 1) {
              // 这里发送一个心跳，后端收到后，返回一个心跳消息，
              // onmessage拿到返回的心跳就说明连接正常
              this.socket.send(JSON.stringify({ 'data': 'ping' }));
              self.serverTimeoutObj = setTimeout(function() { // 如果超过一定时间还没重置，说明后端主动断开了
              }, self.timeout);
            }
          }, this.timeout);
        }
      };
      this.socketOnClose();
      this.socketOnOpen();
      this.socketOnError();
      const self = this;
      // 如果获得到消息、心跳检测重置
      this.socket.onmessage = function(msg) {
        // 拿到任何消息都说明当前连接是正常的
        self.heartCheck.reset().start();
        // 输出获取的数据
        self.read_file_as_text(msg.data, self.term_write, self.decoder);
      };
    },
    // 把接收的数据写到这个插件的屏幕上
    term_write (text) {
      if (text !== 'pong') {
        try {
          const obj = JSON.parse(text);
          // WebSocket关闭的原因
          this.term.writeln('');
          this.term.writeln(obj.reasonCN);
        } catch (SyntaxError) {
          if (this.term) {
            this.term.write(text);
          }
        }
        if (!this.term.resized) {
          this.resize_terminal(this.term);
          this.term.resized = true;
        }
      }
    },
    socketOnOpen () {
      this.socket.onopen = () => {
        this.heartCheck.reset().start(); // 心跳检测重置
        // 链接成功后
        if (this.type === 'CPE') {
          this.term.focus();
          let cpe = JSON.parse(sessionStorage.getItem('shellCPE'));
          this.welcome++;
          let ip = cpe.ipAddress.indexOf('/') > -1 ? cpe.ipAddress.substr(0, cpe.ipAddress.indexOf('/')) : cpe.ipAddress;
          setTimeout(() => {
            this.term.paste('telnet ' + ip + ' vrf ' + cpe.vrfName + '\n');
          }, 600);
        }
      };
    },
    resize_terminal (term) {
      const geometry = this.current_geometry(term);
      term.resize(geometry.cols, geometry.rows);
    },
    // socket 关闭
    socketOnClose () {
      this.socket.onclose = (e) => {
        if (e.reason) {
          this.term.writeln('');
          this.term.writeln(e.reason);
          if (!this.term.resized) {
            const geometry = this.current_geometry(this.term);
            this.term.resize(geometry.cols, geometry.rows);
            this.term.resized = true;
          }
        }
      };
    },
    // socket 错误
    socketOnError () {
      this.socket.onerror = () => {
        this.term.writeln('');
        this.term.writeln('链接失败！');
        if (!this.term.resized) {
          const geometry = this.current_geometry(this.term);
          this.term.resize(geometry.cols, geometry.rows);
          this.term.resized = true;
        }
      };
    },
    // Loading状态处理
    dealLoading (data) {
      if (data.loading) {
        if (this.term) {
          this.welcome++;
          this.term.writeln('');
          this.term.writeln('Connecting ' + data.hostname + '...');
          if (!this.term.resized) {
            const geometry = this.current_geometry(this.term);
            this.term.resize(geometry.cols, geometry.rows);
            this.term.resized = true;
          }
        }
      }
    },
    // 重连
    reconnect (data) {
      if (data.flag) {
        this.welcome++;
        this.term.writeln('');
        this.term.writeln('Connecting ' + data.hostname + '...');
        if (!this.term.resized) {
          const geometry = this.current_geometry(this.term);
          this.term.resize(geometry.cols, geometry.rows);
          this.term.resized = true;
        }
      }
      this.initSocket(data);
    },
    setFocus () {
      setTimeout(() => {
        this.term.focus();
      }, 500);
    },
    current_geometry (term) {
      if (!this.style.width || !this.style.height) {
        try {
          this.get_cell_size(term);
        } catch (TypeError) {
          this.parse_xterm_style();
        }
      }
      const cols = parseInt(window.innerWidth / this.style.width, 10) - 2;
      const rows = parseInt(window.innerHeight / this.style.height, 10);
      return { 'cols': cols, 'rows': rows };
    },
    get_cell_size () {
      this.style.width = this.term._core._renderService._renderer.dimensions.actualCellWidth;
      this.style.height = this.term._core._renderService._renderer.dimensions.actualCellHeight;
    },
    parse_xterm_style () {
      const text = $('.xterm-helpers style').text();
      let arr = text.split('xterm-normal-char{width:');
      this.style.width = parseFloat(arr[1]);
      arr = text.split('div{height:');
      this.style.height = parseFloat(arr[1]);
    },
    read_file_as_text (file, callback, decoder) {
      if (!window.TextDecoder) {
        this.read_as_text_with_encoding(file, callback, decoder);
      } else {
        this.read_as_text_with_decoder(file, callback, decoder);
      }
    },
    read_as_text_with_encoding (file, callback, encoding) {
      let reader = new window.FileReader();
      if (encoding === undefined) {
        encoding = 'utf-8';
      }
      reader.onload = function() {
        if (callback) {
          callback(reader.result);
        }
      };
      reader.onerror = function(e) {
        this.$message.error(e);
      };
      reader.readAsText(file, encoding);
    },
    read_as_text_with_decoder (file, callback, decoder) {
      let reader = new window.FileReader();
      if (decoder === undefined) {
        decoder = new window.TextDecoder('utf-8', { 'fatal': true });
      }
      if ((typeof file === 'string') && file.constructor === String) {
        callback(file);
        reader.readAsArrayBuffer(file);
      } else {
        reader.onload = function() {
          let text;
          try {
            text = decoder.decode(reader.result);
          } catch (TypeError) {
          } finally {
            if (callback) {
              callback(text);
            }
          }
        };
        reader.onerror = function(e) {
          this.$message.error(e);
        };
        reader.readAsArrayBuffer(file);
      }
    }
  }
};
</script>
<style lang="scss" scoped>
</style>